import { FormsModule } from '@angular/forms';
import { ChangeDetectionStrategy, Component, TemplateRef, ViewChild } from '@angular/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { TimePickerModule } from './time-picker.module';
import { OverlayService } from '../core/api';

const disabledTime = {
  lvDisabledHour: hour => {
    if (hour === 0 || hour === 2) {
      return true;
    }
  },
  lvDisabledMinute: (hour, minute) => {
    if (hour === 3 && minute < 30) {
      return true;
    }
  },
  lvDisabledSecond: (hour, minute, second) => {
    if (minute < 30 && second > 30) {
      return true;
    }
  },
};

@Component({
  template: `
    <lv-time-picker
      [(ngModel)]="date"
      [lvSize]="lvSize"
      [lvHeader]="lvHeader"
      [lvFooter]="lvFooter"
      [lvFormat]="lvFormat"
      [lvReadonly]="lvReadonly"
      [lvDisabled]="lvDisabled"
      [lvShowClear]="lvShowClear"
      [lvPlaceholder]="lvPlaceholder"
      [lvDisabledHour]="lvDisabledHour"
      [lvDisabledMinute]="lvDisabledMinute"
      [lvDisabledSecond]="lvDisabledSecond"
      [lvNowAsBaseTime]="lvNowAsBaseTime"
      [lvTimezoneOffset]="lvTimezoneOffset"
      (ngModelChange)="modelChange($event)"
      (lvOpenChange)="openChange()"
    ></lv-time-picker>

    <ng-template #header>
      <div style="display: flex; align-items: center; justify-content: space-around;">
        <span>Hour</span>
        <span>Minute</span>
        <span>Second</span>
      </div>
    </ng-template>
    <ng-template #footer>
      <div
        style="line-height: 24px; font-size: 12px; color: #999; background: rgb(248, 248, 248); text-align: center; border-top:1px solid #e6e6e6"
      >
        当前时区 UTC+8:00
      </div>
    </ng-template>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
class TestTimePickerComponent {
  @ViewChild('header') headerTpl: TemplateRef<Element>;
  @ViewChild('footer') footerTpl: TemplateRef<Element>;

  date = new Date('2023-07-30 10:30:30');
  lvSize: 'default' | 'large' = 'default';
  lvHeader;
  lvFooter;
  lvFormat = 'HH:mm:ss';
  lvReadonly = false;
  lvDisabled = false;
  lvShowClear = true;
  lvPlaceholder = 'HH:mm:ss';
  lvDisabledHour;
  lvDisabledMinute;
  lvDisabledSecond;
  lvNowAsBaseTime = false;
  lvTimezoneOffset: number;

  modelChange(e) {}

  openChange() {}
}

describe('TimePickerComponent', () => {
  beforeEach(waitForAsync(() => {
    TestBed.configureTestingModule({
      declarations: [TestTimePickerComponent],
      imports: [FormsModule, TimePickerModule, NoopAnimationsModule],
      teardown: { destroyAfterEach: true },
      providers: [OverlayService],
    }).compileComponents();
  }));

  describe('render', () => {
    let component: TestTimePickerComponent;
    let fixture: ComponentFixture<TestTimePickerComponent>;

    const delay = () => {
      tick(500);
      fixture.detectChanges();
    };

    const renderComponent = (param, genSnapshot = true) => {
      for (const [key, value] of Object.entries(param)) {
        component[key] = value;
      }
      delay();
      delay();
      if (genSnapshot) {
        expect(fixture).toMatchSnapshot();
      }
    };

    const openPanel = () => {
      const input = fixture.nativeElement.querySelector('.lv-time-picker-main .lv-input');
      input.dispatchEvent(new Event('click'));
      delay();
      delay();
    };

    const getPanel = () => document.querySelector('lv-time-picker-panel');

    beforeEach(() => {
      fixture = TestBed.createComponent(TestTimePickerComponent);
      component = fixture.componentInstance;
    });

    it('with basic', fakeAsync(() => {
      renderComponent({});
      openPanel();
      expect(getPanel()).toMatchSnapshot();
    }));

    it('with size large', fakeAsync(() => {
      renderComponent({ lvSize: 'large' });
    }));

    it('with basic', fakeAsync(() => {
      renderComponent({});
      openPanel();
      expect(getPanel()).toMatchSnapshot();
    }));

    it('with custom header and footer', fakeAsync(() => {
      renderComponent({ lvHeader: component.headerTpl, lvFooter: component.footerTpl }, false);
      openPanel();
      expect(getPanel()).toMatchSnapshot();
    }));

    it('with disbled time', fakeAsync(() => {
      renderComponent(disabledTime, false);
      openPanel();
      expect(getPanel()).toMatchSnapshot();
    }));

    it('with disabled', fakeAsync(() => {
      renderComponent({ lvDisabled: true });
    }));

    it('with format of HH:mm', fakeAsync(() => {
      renderComponent({ lvPlaceholder: 'HH:mm', lvFormat: 'HH:mm' });
      openPanel();
      expect(getPanel()).toMatchSnapshot();
    }));

    it('with format of mm:ss', fakeAsync(() => {
      renderComponent({ lvPlaceholder: 'mm:ss', lvFormat: 'mm:ss' });
      openPanel();
      expect(getPanel()).toMatchSnapshot();
    }));
  });

  describe('work', () => {
    let component: TestTimePickerComponent;
    let fixture: ComponentFixture<TestTimePickerComponent>;

    const delay = () => {
      tick(500);
      fixture.detectChanges();
    };

    const event = (el: Element, type = 'click') => {
      el.dispatchEvent(new Event(type));
    };

    const renderComponent = param => {
      for (const [key, value] of Object.entries(param)) {
        component[key] = value;
      }
      delay();
      delay();
    };

    const getInput = () => fixture.nativeElement.querySelector('.lv-time-picker-main .lv-input');

    const openPanel = () => {
      event(getInput());
      delay();
      delay();
    };

    const getPanel = () => document.querySelector('lv-time-picker-panel');

    const getHours = () => getPanel().querySelectorAll('.lv-time-picker-panel-hour li');

    const getMinutes = () => getPanel().querySelectorAll('.lv-time-picker-panel-minute li');

    const getSeconds = () => getPanel().querySelectorAll('.lv-time-picker-panel-second li');

    beforeEach(() => {
      fixture = TestBed.createComponent(TestTimePickerComponent);
      component = fixture.componentInstance;
    });

    it('with basic', fakeAsync(() => {
      renderComponent({});
      openPanel();
      event(getHours()[20]);
      event(getMinutes()[20]);
      event(getSeconds()[20]);
      delay();
      delay();
      expect(getInput().value).toContain('20:20:20');
    }));

    // 点击“此刻”按钮
    it('with atTheMoment button', fakeAsync(() => {
      jest.spyOn(component, 'modelChange');
      renderComponent({});
      openPanel();
      const button = getPanel().querySelector('.lv-time-picker-panel-operation .lv-button-type-link');
      const date = new Date();
      event(button);
      delay();
      expect(component.modelChange).toHaveBeenCalledWith(date);
    }));

    it('with clear', fakeAsync(() => {
      renderComponent({});
      const clear = fixture.nativeElement.querySelector('.lv-time-picker-clear');
      event(clear);
      delay();
      delay();
      expect(getInput().value).toBeFalsy();
    }));

    it('with disabled time', fakeAsync(() => {
      jest.spyOn(component, 'modelChange');
      renderComponent(disabledTime);
      openPanel();
      event(getHours()[0]);
      delay();
      delay();
      expect(component.modelChange).toHaveBeenCalledTimes(0);
      event(getHours()[3]);
      delay();
      delay();
      expect(component.modelChange).toHaveBeenCalledTimes(1);
      event(getMinutes()[20]);
      delay();
      delay();
      expect(component.modelChange).toHaveBeenCalledTimes(1);
      event(getHours()[1]);
      delay();
      delay();
      expect(component.modelChange).toHaveBeenCalledTimes(2);
      event(getMinutes()[20]);
      delay();
      delay();
      expect(component.modelChange).toHaveBeenCalledTimes(3);
      event(getSeconds()[40]);
      delay();
      delay();
      expect(component.modelChange).toHaveBeenCalledTimes(3);
      expect(getInput().value).toContain('01:20:30');
      event(getSeconds()[29]);
      delay();
      delay();
      expect(component.modelChange).toHaveBeenCalledTimes(4);
      expect(getInput().value).toContain('01:20:29');
    }));
  });
});
